﻿// Decompiled with JetBrains decompiler
// Type: MCM.LightInject.Emitter
// Assembly: MCMv5, Version=5.10.2.0, Culture=neutral, PublicKeyToken=null
// MVID: 7AB326B9-B2B4-41C2-9177-68E944071101
// Assembly location: D:\steam\steamapps\workshop\content\261550\2859238197\bin\Win64_Shipping_Client\MCMv5.dll

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Emit;

#nullable disable
namespace MCM.LightInject
{
  [ExcludeFromCodeCoverage]
  internal class Emitter : IEmitter
  {
    private readonly ILGenerator generator;
    private readonly Type[] parameterTypes;
    private readonly Stack<Type> stack = new Stack<Type>();
    private readonly List<LocalBuilder> variables = new List<LocalBuilder>();
    private readonly List<Instruction> instructions = new List<Instruction>();

    public Emitter(ILGenerator generator, Type[] parameterTypes)
    {
      this.generator = generator;
      this.parameterTypes = parameterTypes;
    }

    public Type StackType => this.stack.Count != 0 ? this.stack.Peek() : (Type) null;

    public List<Instruction> Instructions => this.instructions;

    public void Emit(OpCode code)
    {
      if (code == OpCodes.Ldarg_0)
        this.stack.Push(this.parameterTypes[0]);
      else if (code == OpCodes.Ldarg_1)
        this.stack.Push(this.parameterTypes[1]);
      else if (code == OpCodes.Ldarg_2)
        this.stack.Push(this.parameterTypes[2]);
      else if (code == OpCodes.Ldarg_3)
        this.stack.Push(this.parameterTypes[3]);
      else if (code == OpCodes.Ldloc_0)
        this.stack.Push(this.variables[0].LocalType);
      else if (code == OpCodes.Ldloc_1)
        this.stack.Push(this.variables[1].LocalType);
      else if (code == OpCodes.Ldloc_2)
        this.stack.Push(this.variables[2].LocalType);
      else if (code == OpCodes.Ldloc_3)
        this.stack.Push(this.variables[3].LocalType);
      else if (code == OpCodes.Stloc_0)
        this.stack.Pop();
      else if (code == OpCodes.Stloc_1)
        this.stack.Pop();
      else if (code == OpCodes.Stloc_2)
        this.stack.Pop();
      else if (code == OpCodes.Stloc_3)
        this.stack.Pop();
      else if (code == OpCodes.Ldelem_Ref)
      {
        this.stack.Pop();
        this.stack.Push(this.stack.Pop().GetElementType());
      }
      else if (code == OpCodes.Ldlen)
      {
        this.stack.Pop();
        this.stack.Push(typeof (int));
      }
      else if (code == OpCodes.Conv_I4)
      {
        this.stack.Pop();
        this.stack.Push(typeof (int));
      }
      else if (code == OpCodes.Ldc_I4_0)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_1)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_2)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_3)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_4)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_5)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_6)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_7)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldc_I4_8)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Sub)
      {
        this.stack.Pop();
        this.stack.Pop();
        this.stack.Push(typeof (int));
      }
      else if (!(code == OpCodes.Ret))
      {
        if (!(code == OpCodes.Ldnull))
          throw new NotSupportedException(code.ToString());
        this.stack.Push((Type) null);
      }
      this.instructions.Add(new Instruction(code, (Action<ILGenerator>) (il => il.Emit(code))));
      if (!(code == OpCodes.Ret))
        return;
      foreach (Instruction instruction in this.instructions)
        instruction.Emit(this.generator);
    }

    public void Emit(OpCode code, int arg)
    {
      if (code == OpCodes.Ldc_I4)
        this.stack.Push(typeof (int));
      else if (code == OpCodes.Ldarg)
        this.stack.Push(this.parameterTypes[arg]);
      else if (code == OpCodes.Ldloc)
        this.stack.Push(this.variables[arg].LocalType);
      else if (code == OpCodes.Ldloca)
      {
        this.stack.Push(this.variables[arg].LocalType.MakePointerType());
      }
      else
      {
        if (!(code == OpCodes.Stloc))
          throw new NotSupportedException(code.ToString());
        this.stack.Pop();
      }
      this.instructions.Add((Instruction) new Instruction<int>(code, arg, (Action<ILGenerator>) (il => il.Emit(code, arg))));
    }

    public void Emit(OpCode code, long arg)
    {
      if (!(code == OpCodes.Ldc_I8))
        throw new NotSupportedException(code.ToString());
      this.stack.Push(typeof (long));
      this.instructions.Add((Instruction) new Instruction<long>(code, arg, (Action<ILGenerator>) (il => il.Emit(code, arg))));
    }

    public void Emit(OpCode code, sbyte arg)
    {
      if (!(code == OpCodes.Ldc_I4_S))
        throw new NotSupportedException(code.ToString());
      this.stack.Push(typeof (int));
      this.instructions.Add((Instruction) new Instruction<int>(code, (int) arg, (Action<ILGenerator>) (il => il.Emit(code, arg))));
    }

    public void Emit(OpCode code, byte arg)
    {
      if (code == OpCodes.Ldloc_S)
        this.stack.Push(this.variables[(int) arg].LocalType);
      else if (code == OpCodes.Ldarg_S)
      {
        this.stack.Push(this.parameterTypes[(int) arg]);
      }
      else
      {
        if (!(code == OpCodes.Stloc_S))
          throw new NotSupportedException(code.ToString());
        this.stack.Pop();
      }
      this.instructions.Add((Instruction) new Instruction<byte>(code, arg, (Action<ILGenerator>) (il => il.Emit(code, arg))));
    }

    public void Emit(OpCode code, Type type)
    {
      if (code == OpCodes.Newarr)
      {
        this.stack.Pop();
        this.stack.Push(type.MakeArrayType());
      }
      else if (code == OpCodes.Stelem)
      {
        this.stack.Pop();
        this.stack.Pop();
        this.stack.Pop();
      }
      else if (code == OpCodes.Castclass)
      {
        this.stack.Pop();
        this.stack.Push(type);
      }
      else if (code == OpCodes.Box)
      {
        this.stack.Pop();
        this.stack.Push(typeof (object));
      }
      else if (code == OpCodes.Unbox_Any)
      {
        this.stack.Pop();
        this.stack.Push(type);
      }
      else
      {
        if (!(code == OpCodes.Initobj))
          throw new NotSupportedException(code.ToString());
        this.stack.Pop();
      }
      this.instructions.Add((Instruction) new Instruction<Type>(code, type, (Action<ILGenerator>) (il => il.Emit(code, type))));
    }

    public void Emit(OpCode code, ConstructorInfo constructor)
    {
      if (!(code == OpCodes.Newobj))
        throw new NotSupportedException(code.ToString());
      int length = constructor.GetParameters().Length;
      for (int index = 0; index < length; ++index)
        this.stack.Pop();
      this.stack.Push(constructor.DeclaringType);
      this.instructions.Add((Instruction) new Instruction<ConstructorInfo>(code, constructor, (Action<ILGenerator>) (il => il.Emit(code, constructor))));
    }

    public void Emit(OpCode code, LocalBuilder localBuilder)
    {
      if (code == OpCodes.Stloc)
      {
        this.stack.Pop();
      }
      else
      {
        if (!(code == OpCodes.Ldloc))
          throw new NotSupportedException(code.ToString());
        this.stack.Push(localBuilder.LocalType);
      }
      this.instructions.Add((Instruction) new Instruction<LocalBuilder>(code, localBuilder, (Action<ILGenerator>) (il => il.Emit(code, localBuilder))));
    }

    public void Emit(OpCode code, MethodInfo methodInfo)
    {
      if (!(code == OpCodes.Callvirt) && !(code == OpCodes.Call))
        throw new NotSupportedException(code.ToString());
      int length = methodInfo.GetParameters().Length;
      for (int index = 0; index < length; ++index)
        this.stack.Pop();
      if (!methodInfo.IsStatic)
        this.stack.Pop();
      if (methodInfo.ReturnType != typeof (void))
        this.stack.Push(methodInfo.ReturnType);
      this.instructions.Add((Instruction) new Instruction<MethodInfo>(code, methodInfo, (Action<ILGenerator>) (il => il.Emit(code, methodInfo))));
    }

    public LocalBuilder DeclareLocal(Type type)
    {
      LocalBuilder localBuilder = this.generator.DeclareLocal(type);
      this.variables.Add(localBuilder);
      return localBuilder;
    }

    public void Emit(OpCode code, string arg)
    {
      if (!(code == OpCodes.Ldstr))
        throw new NotSupportedException(code.ToString());
      this.stack.Push(typeof (string));
      this.instructions.Add((Instruction) new Instruction<string>(code, arg, (Action<ILGenerator>) (il => il.Emit(code, arg))));
    }

    public void PushConstantValue(object arg, Type type)
    {
      this.stack.Push(type);
      this.instructions.Add((Instruction) new Instruction<object>(OpCodes.Nop, arg, (Action<ILGenerator>) (il => il.PushConstantValue(arg, type))));
    }
  }
}
